
//
// 120428 - AH - Created.
// 120430 - AH - toString().
// 120501 - AH - Renamed from HtmlForm: That it can generate an HTML form is not
//               main feature. That is mostly for testing or 'API type of 
//               pages'.
// 120502a- AH - addParameter(String).
// 120502b- AH - Renamed from HttpParameters.
// 120503 - AH - Desc and info line properties.
// 120507 - AH - enctype property.
// 120513 - AH - Fixed that parameter(String, String) did not add created
//               parameter.
// 130310 - AH - toXml methods.
// 130311 - AH - authentication() and authentication(boolean).
// 130313 - AH - action() and post() (was missing!).
// 130316 - AH - Now extends HttpProtocolDef, name and desc property code
//               removed from here.
//

package org.japril.servlet;

import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

/**
 * <p>
 *   Represents the
 *   {@link HttpRequestParamDef}s of an HTTP request.
 * </p>
 * @author Arne Halvorsen (AH)
 */
public final class HttpRequestDef extends HttpProtocolElement 
{
  // Does not provide annotation for.
  private final List<String> m_info=new ArrayList<String>(); // get.
  
  private final List<HttpRequestParamDef> m_params=  // annotation, get.
    new ArrayList<HttpRequestParamDef>();
  
  private final boolean m_post; // annotation, get.
  
  private final String m_action; // annotation, get.
  
  private String m_enctype=null; // annotation, get.
  
  private boolean m_authentication=false; // annotation, get.
  
  /**
   * <p>
   *   Constructor.
   * </p>
   * <p>
   *   Request method will be <i>get</i>.
   * </p>
   * @param action Request's relative URL. This is a form's 'action' parameter 
   *               value.
   */
  public HttpRequestDef(String action){ this(false, action); }
  
  /**
   * <p>
   *   Constructor.
   * </p>
   * @param post   {@code true} if <i>post</i> request, {@code false} (default)
   *               if <i>get</i> request.
   * @param action Request's relative URL. This is a form's 'action' parameter 
   *               value.
   * @throws IllegalArgumentException If {@code action} is the empty string. 
   */
  public HttpRequestDef(boolean post, String action)
  {
    super("input");
    
    if (action==null)
    {
      throw new NullPointerException("action");
    }
    
    action=action.trim();
    if (action.length()==0)
    {
      throw new IllegalArgumentException("action is the empty string");
    }
    
    m_post=post;
    m_action=action;
  }
  
  /**
   * <p>
   *   Gets request's relative.
   * </p>
   * <p>
   *   This is a form's 'action' parameter value.
   * </p>
   * @return URL.
   */
  public String action(){ return m_action; }
  
  /**
   * <p>
   *   Gets if post method to be used.
   * </p>
   * @return {@code true} if post method is to be used, {@code false} if get
   *         method is to be used.
   */
  public boolean post(){ return m_post; }
  
  /**
   * <p>
   *   Sets the encoding type of request's reply. Default is that it is not
   *   set. 
   * </p>
   * @param type Type to set.
   * @return {@code this}.
   * @throws IllegalArgumentException If {@code type} is the empty string. 
   */
  public HttpRequestDef enctype(String type)
  {
    if (type==null)
    {
      throw new NullPointerException("type");
    }
 
    type=type.trim();
    if (type.length()==0)
    {
      throw new IllegalArgumentException("type is the empty string");
    }
    
    m_enctype=type;
    
    return this;
  }
  
  /**
   * <p>
   *   Gets the encoding type of request's reply.
   * </p>
   * @return Type or {@code null} if not sat.
   */
  public String enctype(){ return m_enctype; }
  
  /**
   * <p>
   *   Adds information line.
   * </p>
   * @param l Line, can not be {@code null} but can be the empty string.
   * @return {@code this}.
   */
  public HttpRequestDef infoLine(String l)
  {
    if (l==null)
    {
      throw new NullPointerException("l");
    }
    
    m_info.add(l.trim());
    return this;
  }
  
  /**
   * <p>
   *   Gets information lines.
   * </p>
   * @return Lines.
   */
  public String[] infoLines(){ return m_info.toArray(E_STRING_A); } 
  
  /**
   * <p>
   *   Convenient method to create and add a default parameter.
   * </p>
   * @param name Parameter's name.
   * @param desc Description. Optional, may be {@code null}.
   * @return Created.
   */
  public HttpRequestParamDef parameter(String name, String desc)
  {
    HttpRequestParamDef p=new HttpRequestParamDef(name);
    if (desc!=null) p.description(desc);
    parameter(p);
    return p;
  }
  
  /**
   * <p>
   *   Adds parameter.
   * </p>
   * @param param Parameter to add.
   * @return {@code this}.
   * @throws IllegalArgumentException If already has parameter with same name
   *         as given.
   */
  public HttpRequestDef parameter(HttpRequestParamDef param)
  {
    if (param==null)
    {
      throw new NullPointerException("param");
    }
    if (hasParameter(param.name()))
    {
      throw new IllegalArgumentException("duplicate");
    }
    
    m_params.add(param);
    
    return this;
  }
  
  /**
   * <p>
   *   Tells if form has named parameter.
   * </p>
   * @param name Name on parameter to seek.
   * @return {@code true} if found, {@code false} if not found.
   */
  public boolean hasParameter(String name)
  {
    if (name==null)
    {
      throw new NullPointerException("name");
    }
    
    for (HttpRequestParamDef curr : m_params)
    {
      if (name.equals(curr.name())) return true;  
    }
    return false;
  }
  
  /**
   * <p>
   *   Gets number of parameters.
   * </p>
   * @return Count.
   */
  public int parameterCount(){ return m_params.size(); }
  
  /**
   * <p>
   *   Gets index of parameter.
   * </p>
   * @param name Name of parameter.
   * @return Index of parameter or {@code -1} if not found.
   */
  public int indexOfParameter(String name)
  {
    if (name==null)
    {
      throw new NullPointerException("name");
    }
    
    int n=m_params.size();
    for (int i=0; i<n; i++)
    {
      HttpRequestParamDef param=m_params.get(i);
      if (param.name().equals(name)) return i;
    }
    return -1;
  }
  
  /**
   * <p>
   *   Gets
   *   {@link HttpRequestParamDef}.
   * </p>
   * @param idx Index of parameter to get.
   * @return Parameter.
   * @throws IndexOutOfBoundsException If 
   *         {@code idx<0 || idx>=getParameterCount()}.
   */
  public HttpRequestParamDef parameter(int idx){ return m_params.get(idx); }
  
  /**
   * <p>
   *   Gets
   *   {@link HttpRequestParamDef}.
   * </p>
   * @param name Name of parameter to get.
   * @return Parameter or {@code null} if not found.
   */
  public HttpRequestParamDef parameter(String name)
  {
    if (name==null)
    {
      throw new NullPointerException("name");
    }
    
    int idx=indexOfParameter(name);
    return idx==-1 ? null : parameter(idx);
  }
  
  /**
   * <p>
   *   Sets if request required authentication or not.
   * </p>
   * @param v {@code true} if requires, {@code false} (default) if does not.
   * @return {@code this}.
   */
  public HttpRequestDef authentication(boolean v)
  {
    m_authentication=v;
    return this;
  }
  
  /**
   * <p>
   *   Gets if request required authentication or not.
   * </p>
   * @return {@code true} if requires, {@code false} (default) if does not.
   */
  public boolean authentication(){ return m_authentication; }
  
  /**
   * <p>
   *   Fetch parameter for named parameter from the given
   *   {@link HttpServletRequest}.
   * </p>
   * @param name Parameter name.
   * @param req  Request to fetch from.
   * @return {@link FetchParameterResult}.
   * @throws IllegalArgumentException If no parameter def found.
   */
  public FetchParameterResult fetch(String name, HttpServletRequest req)
  {
    if (name==null)
    {
      throw new NullPointerException("name");
    }
    if (req==null)
    {
      throw new NullPointerException("req");
    }
    
    HttpRequestParamDef par=parameter(name);
    if (par==null)
    {
      throw new IllegalArgumentException("Uknown HTTP request parameter: "+
        name);
    }
    
    return par.getParameter(req);
  }
  
  /**
   * <p>
   *   Validates a given
   *   {@link HttpServletRequest} for a request defined by {@code this}.
   * </p>
   * @param req {@code HttpServletRequest} to validate.
   * @return Error message if not accepted, {@code null} if accepted.
   */
  public String validate(HttpServletRequest req)
  {
    if (req==null)
    {
      throw new NullPointerException("req");
    }
    
    StringBuilder sb=new StringBuilder();
    
    boolean first=true;
    for (HttpRequestParamDef par : m_params)
    {
      String err=par.validate(req);
      if (err!=null)
      {
        if (!first) sb.append(", ");
        sb.append(err);
        first=false;
      }
    }
    
    return sb.length()==0 ? null : sb.toString();
  }
  
  /**
   * <p>
   *   Creates HTML for API describing page.
   * </p>
   * @return HTML.
   */
  public String compileApiHtml()
  {
    CharArrayWriter caw=new CharArrayWriter();
    PrintWriter pw=new PrintWriter(caw);
    
    StringBuilder sb=new StringBuilder();
   
    String desc=description();
    if (desc!=null)
    {
      sb.setLength(0);
      sb.append("<h1>").append(desc).append("</h1>");
      pw.println(sb.toString());
    }
    
    if (!m_info.isEmpty())
    {
      pw.println("<p>");
      for (String line : m_info)
      {
        sb.setLength(0);
        sb.append(line).append("<br />");
        pw.println(sb.toString());
      }
      pw.println("</p>");
    }
    
    String auth=authentication() ? "Yes" : "No";
    pw.println("<p>Requires authentication: "+auth+"</p>");
    
    String method=m_post ? "post" : "get";
    
    sb.setLength(0);
    sb.append("<form name='").append(name()).append("' action='")
      .append(m_action).append("' method='").append(method).append("'");
    
    if (m_enctype!=null)
    {
      sb.append(" enctype='").append(m_enctype).append("'");
    }
    
    sb.append(">");
    
    pw.println(sb.toString());
    
    boolean submitDone=false;
    for (HttpRequestParamDef p : m_params)
    {
      boolean isSubmit=p.isSubmit();
      if (!isSubmit || !submitDone)
      {
        sb.setLength(0);
        sb.append(p.compileApiHtml()).append("<br />");
        pw.println(sb.toString());
        if (isSubmit) submitDone=true;
      }
    }
    
    if (!submitDone)
    {
      pw.println("<input type='submit' value='Submit' /><br />");
    }
    
    pw.println("</form>");
    
    pw.flush();
    return caw.toString();
  }
  
  /**
   * <p>
   *   Creates complete stand alone HTML page with form for test purpose. 
   * </p>
   * @return HTML.
   */
  public String compileStandAloneHtml()
  {
    CharArrayWriter caw=new CharArrayWriter();
    PrintWriter pw=new PrintWriter(caw);
    
    pw.println("<html><body>");
    pw.println(compileApiHtml());
    pw.println("</body></html>");
    
    pw.flush();
    return caw.toString();
  }
  
  /**
   * <p>
   *   Gets XML representation.
   * </p>
   * @return XML.
   */
  public String toXml(){ return toXml(true, ""); }
  
  /**
   * <p>
   *   Gets XML representation.
   * </p>
   * @param standalone If to get standalone XML.
   * @return XML.
   */
  public String toXml(boolean standalone){ return toXml(standalone, ""); }
  
  /**
   * <p>
   *   Gets XML representation.
   * </p>
   * @param standalone If to get standalone XML.
   * @param indent     Indent to start at.
   * @return XML.
   */
  public String toXml(boolean standalone, String indent)
  {
    CharArrayWriter caw=new CharArrayWriter();
    PrintWriter pw=new PrintWriter(caw);
    
    StringBuilder sb=new StringBuilder();
    
    if (standalone)
    {
      sb.setLength(0);
      sb.append(indent).append("<?xml version='1.0'?>");
      pw.println(sb);
    }
    
    sb.append(indent).append("<http-request name='").append(name())
      .append("' post='").append(m_post).append("' action='").append(m_action)
      .append("'");
    
    if (m_enctype!=null) sb.append(" enctype='").append(m_enctype).append("'");
    
    sb.append(">");
    pw.println(sb);
    
    String desc=description();
    if (desc!=null)
    {
      sb.setLength(0);
      sb.append(indent).append("  <desc>").append(desc).append("</desc>");
      pw.println(sb);
    }
    
    if (!m_info.isEmpty())
    {
      sb.setLength(0);
      sb.append(indent).append("  <req-info>");
      pw.println(sb);
      for (String line : m_info)
      {
        sb.setLength(0);
        if (line.length()==0)
        {
          sb.append(indent).append("    <line/>");
        }
        else
        {
          sb.append(indent).append("    <line>").append(line).append("</line>");
        }
        pw.println(sb);
      }
      sb.setLength(0);
      sb.append(indent).append("  </req-info>");
      pw.println(sb);
    }
    
    if (m_params.size()>0)
    {
      sb.setLength(0);
      sb.append(indent).append("  <http-parameters>");
      
      for (HttpRequestParamDef p : m_params)
      {
        String pxml=p.toXml(false, indent+"  ");
        pw.println(pxml);
      }
      
      sb.setLength(0);
      sb.append(indent).append("  </http-parameters>");
    }
   
    sb.setLength(0);  
    sb.append(indent).append("</http-request>");
    pw.println(sb);
    
    pw.flush();
    return caw.toString();
  }
  
  @Override
  public String toString() 
  {
    CharArrayWriter caw=new CharArrayWriter();
    PrintWriter pw=new PrintWriter(caw);
    
    StringBuilder sb=new StringBuilder();
   
    sb.append("name=").append(name()).append(", method=");
    if (m_post) sb.append("post");
    else        sb.append("get");
    sb.append(", action=").append(m_action).append(", authentication=")
      .append(m_authentication);
    if (m_enctype!=null) sb.append(", enctype=").append(m_enctype);
    pw.println(sb.toString());
    
    if (!m_params.isEmpty())
    {
      pw.println("parameters:");
      for (HttpRequestParamDef param : m_params) pw.println(param);
    }
    
    pw.flush();
    return caw.toString();
  }
  
  private final static String[] E_STRING_A=new String[0];
  
  /**
   * <p>
   * </p>
   * @param args
   */
  public static void main(String[] args) 
  {
    try
    {
      HttpRequestDef form=new HttpRequestDef("http://test");
      
      HttpRequestParamDef p=new HttpRequestParamDef("p1");
      form.parameter(p);
      
      p=new HttpRequestParamDef("p2")
        .desc("Default parameter with this description");
      form.parameter(p);
      
      p=new HttpRequestParamDef("p3")
        .desc("Default parameter with this description")
        .info("and information");
      form.parameter(p);
      
      p=new HttpRequestParamDef("p4")
        .desc("Default parameter with this description")
        .mandatory(false)
        .info("this is not mandatory");
      form.parameter(p);
      
      System.out.println(form);
      
      String html=form.compileStandAloneHtml();
      
      File f=new File("./formtest.html");
      FileWriter fw=new FileWriter(f);
      
      fw.write(html);
      fw.flush();
    }
    catch (IOException iox)
    {
      iox.printStackTrace();
      System.exit(-1);
    }
  }
  
}
